package com.sihan.framework.etm.app.service.impl;

import com.framework.constant.WebConstants;
import com.framework.exception.AuthException;
import com.framework.exception.BusinessException;
import com.framework.mybatis.Condition;
import com.framework.mybatis.service.impl.BaseServiceImpl;
import com.framework.util.JsonUtils;
import com.framework.util.WebUtils;
import com.framework.util.crypto.symmetric.SM4;
import com.framework.util.date.DatePattern;
import com.framework.util.date.DateUtils;
import com.framework.web.websocket.WebSocketServer;
import com.sihan.framework.etm.app.service.AppLoginService;
import com.sihan.framework.etm.common.BizErrorItemModel;
import com.sihan.framework.etm.common.BooleanResultModel;
import com.sihan.framework.etm.entity.Log;
import com.sihan.framework.etm.entity.SecurityParam;
import com.sihan.framework.etm.entity.User;
import com.sihan.framework.etm.enums.ErrorType;
import com.sihan.framework.etm.enums.RoleType;
import com.sihan.framework.etm.mapper.UserMapper;
import com.sihan.framework.etm.service.*;
import com.sihan.framework.etm.vo.LoginVO;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;

@Slf4j
@Service
@PropertySource("classpath:application.yml")
public class AppLoginServiceImpl extends BaseServiceImpl<User, String> implements AppLoginService {

	@Autowired
	private UserMapper userMapper;
	  
	@Autowired
	private RoleService roleService;
	@Autowired
	private LogService logService;
	
	@Autowired
	private SecurityParamService securityParamService;
	
	@Autowired
	private SessionDAO sessionDAO;
	
	@Transactional
	@Override
	public Object updatePassword(String oldPassword, String newPassword) {
		User user = WebUtils.user();
	    // 比较密码是否相同
	    String oldPasswordSM4;
	    String newPasswordSM4;
	    try {
	        oldPasswordSM4 = SM4.encode(oldPassword, SM4.getKey());
	        newPasswordSM4 = SM4.encode(newPassword, SM4.getKey());
	    } catch (IOException e) {
	        log.error("更新密码异常",e);
	        throw new BusinessException("未知异常，联系管理员");
	    }
	    if (!user.getPassword().equals(oldPasswordSM4)) {
	        return BizErrorItemModel.USER_ERROR_100012;
	    }
	
	    if(oldPassword.equals(newPassword)) {
	    	return BizErrorItemModel.USER_ERROR_100013;
	    }
	
	    if(newPassword.indexOf(user.getUsername()) >= 0) {
	      return BizErrorItemModel.USER_ERROR_100009;
	    }
	    user.setPassword(newPasswordSM4);
	    user.setUpdatePsdTime(new Date());
	    userMapper.updateById(user);
	    return BooleanResultModel.ok();
	}
	
	@Transactional
	@Override
	public Object login(String username, String password, String hashId) throws AuthException {
	    String passwordSM4;
	    LoginVO result = new LoginVO();
	    try {
	      passwordSM4=SM4.encode(password, SM4.getKey());
	    } catch (IOException e) {
	    	log.error("app登录异常",e);
	      throw new BusinessException("未知异常，联系管理员");
	    } 
	
	    User user = userMapper.selectOne(new Condition<>(new User(username)));
	    if(user == null) {
	    	throw new BusinessException("用户名或者密码错误");
	    } else {
	    	if(!passwordSM4.equals(user.getPassword())){
	    		throw new BusinessException("用户名或者密码错误");
	    	}
	        if(user.getState() != null) {
		        if(user.getState().intValue() == -1) {//注销
		        	throw new BusinessException("账户已注销");
		        } else if(user.getState().intValue() == 1) {//休眠
		        	throw new BusinessException("账户休眠");
		        }
	      }
	
	      // 获取当前的Subject
	      Subject subject = SecurityUtils.getSubject();
	
	      String currentIp = subject.getSession().getHost();
	
	      if(StringUtils.isNotBlank(user.getLoginIp())) {
	        if (!user.getLoginIp().equals(currentIp)) {
	          LocalDateTime localDateTime = LocalDateTime.now();
	          String opera = "IP地址异常";
	          StringBuffer content = new StringBuffer();
	          try {
	            content.append("【").append(user.getUsername()).append("】").append(opera);
	            content.append("： “").append(user.getUsername()).append("” ").append(DateUtils.formatDateTime(localDateTime, DatePattern.DATETIME))
	                    .append(" 在IP地址为<").append(currentIp).append(">的客户端试图登录，为非允许的IP地址");
	          } catch (Exception e1) {
	            log.error("app登录异常",e1);
	          }
	
	          Log log = new Log(currentIp, opera, content.toString(), false, 2, ErrorType.GENERAL.getValue(), user.getUsername(), new Date());
	          logService.insert(log);
	          sendErrorMsg(log);
	          throw new BusinessException("登录地址异常");
	        }
	      }
	
	      //当前时间是否允许访问
	      Date now = new Date();
	      if(user.getAccessStartTime() == null && user.getAccessEndTime() != null) {
	        if(now.after(user.getAccessEndTime())) {
	          return BizErrorItemModel.LOGIN_ERROR_100005;
	        }
	      } else if(user.getAccessStartTime() != null && user.getAccessEndTime() == null) {
	        if(now.before(user.getAccessStartTime())) {
	          return BizErrorItemModel.LOGIN_ERROR_100005;
	        }
	      } else if(user.getAccessStartTime() != null && user.getAccessEndTime() != null) {
	        if(now.before(user.getAccessStartTime()) || now.after(user.getAccessEndTime())) {
	          return BizErrorItemModel.LOGIN_ERROR_100005;
	        }
	      }
	
	      if(!subject.isAuthenticated()) {
	        SecurityParam sp = securityParamService.getSecurity();
	        Collection<Session> sessions =  sessionDAO.getActiveSessions();
	        Map<String, Session> map = new HashMap<>();
	
	        if(sessions != null && sessions.size() > 0) {
	          for (Session session : sessions) {
	            if(session != null && session.getId() != null) {
	              User sessionUser = (User) session.getAttribute(WebConstants.CURRENT_USER);
	              if(sessionUser != null) {
	                map.put(sessionUser.getUsername(), session);
	              }
	            }
	          }
	        }
	
	        //当前在线会话数等于最大会话并发数，禁止登陆
	        if(sp != null && sp.getMaxSession() != null && sp.getMaxSession().intValue() > 0) {
	          if(map.size() >= sp.getMaxSession().intValue()) {
	        	  throw new BusinessException("已达到登录最大数");
	          }
	        }
	
	        try{
	          UsernamePasswordToken token = new UsernamePasswordToken(username, passwordSM4);
	          subject.login(token);
	
	          //更新登录时间（数据库对应上次最后访问时间，连续登录失败次数清0）
	          User upUser = new User();
	          upUser.setId(user.getId());
	          upUser.setLastAccessTime(new Date());
	          upUser.setLoginFault(0);
	          userMapper.updateById(upUser);
	
	          subject.getSession().setAttribute("HASH_ID", hashId);
	          subject.getSession().setAttribute(WebConstants.CURRENT_USER,user);
	          //设置session过期时间
	
//	          if(sp != null && sp.getGlobalSessionTimeout() != null && sp.getGlobalSessionTimeout().intValue() > 0 && sp.getGlobalSessionTimeout().intValue() <= 30) {
//	            subject.getSession().setTimeout(sp.getGlobalSessionTimeout().intValue() * 60 * 1000L);
//	          }
	          subject.getSession().setTimeout(-1000000L);
	          result.setSessionId(subject.getSession().getId().toString());
	        } catch (Exception e) {
	          if (e instanceof IncorrectCredentialsException) {
	            //更新登录失败次数+1
	            userMapper.upUserLoginFault(user.getId());
	
	            if(user.getLoginFault().intValue() >= 1) {//连续登录失败告警
	              LocalDateTime localDateTime = LocalDateTime.now();
	              String opera = "连续登录失败";
	              StringBuffer content = new StringBuffer();
	              try {
	                content.append("【").append(user.getUsername()).append("】").append(opera);
	                content.append("： “").append(user.getUsername()).append("” ").append(DateUtils.formatDateTime(localDateTime, DatePattern.DATETIME))
	                        .append(" 在IP地址为<").append(currentIp).append(">的客户端已经连续登录失败")
	                        .append(user.getLoginFault().intValue() + 1).append("次");
	              } catch (Exception e2) {
	                log.error("app登录异常",e2);
	              }
	
	              Log log = new Log(currentIp, opera, content.toString(), false, 2, ErrorType.TRIVIAL.getValue(), user.getUsername(), new Date());
	              logService.insert(log);
	              sendErrorMsg(log);
	            }
	
	            if(null != sp && user.getLoginFault().intValue() + 1 >= sp.getFaultNum().intValue()) {
	              User upUser = new User();
	              upUser.setId(user.getId());
	              Calendar calendar = Calendar.getInstance();
	              upUser.setLockTime(calendar.getTime());
	              upUser.setLocked(true);
	              userMapper.updateById(upUser);
	              //锁定用户
	              //解锁定时任务
	              calendar.add(Calendar.MINUTE, sp.getLockTime());
	              throw new BusinessException("登录失败");
	            }
	            throw new BusinessException("用户名或者密码错误");
	          } else if(e instanceof UnknownAccountException) {
	            throw new UnknownAccountException();
	          } else if(e instanceof LockedAccountException) {
	            throw new LockedAccountException();
	          } else {
	            throw new BusinessException("登录失败,联系管理员");
	          }
	
	        }
	      }else {
	    	  result.setSessionId(subject.getSession().getId().toString());
	      }
	    }
	    return result;
	}
	
	@Transactional
	@Override
	public void resetPassword(String userId, String newPsd) {
	    User user = getUserById(userId);
	    if (user == null) {
	      throw new BusinessException("被重置密码的用户id不存在");
	    }
	
	    String newPasswordSM4;
	    try {
	      newPasswordSM4 = SM4.encode(newPsd, SM4.getKey());
	    } catch (IOException e) {
	    	log.error("重置密码异常",e);
	      throw new BusinessException("未知异常，联系管理员");
	    }
	
	    if(user.getPassword().equals(newPasswordSM4)) {
	      throw new BusinessException("新密码不能和原密码相同");
	    }
	
	    if(newPsd.indexOf(user.getUsername()) >= 0) {
	      throw new BusinessException("新密码不能包含或等于用户名");
	    }
	
	    user.setPassword(newPasswordSM4);
	    userMapper.resetUserPsdByUserId(user);
	}
	
	private void sendErrorMsg(Log log1) {
	    CopyOnWriteArraySet<WebSocketServer> webSocketSet = WebSocketServer.getWebSocketSet();
	    if(webSocketSet != null && webSocketSet.size() > 0) {
	      for (WebSocketServer ws: webSocketSet) {
	        if(ws.getSession().isOpen()) {
	          Set<String> roles = roleService.selectRolesByUsername(ws.getSid());
	          if(roles != null && roles.contains(RoleType.AUDITMANAGER.getValue())) {
	            try {
	              ws.sendMessage(JsonUtils.toJsonString(log1));
	            } catch (IOException e) {
	            	log.error("重置密码异常",e);
	            }
	          }
	        }
	      }
	    }
	}
	
	 /**
	* 根据用户id获取用户
	  * @param userId
	  * @return
	  */
	private User getUserById(String userId) {
	    return userMapper.selectOne(new Condition<>(new User(userId, false)));
	}
	
	@Override
	public int getTaskNum(String userId) {
		// TODO Auto-generated method stub
		return 0;
	}
}
